package shanghaielectric_lib.Communication.Comm_Modbus;

import android.os.Build;
import android.serialport.SerialPort;
import android.support.annotation.RequiresApi;
import android.util.Log;

import com.shanghai.shanghaielectric.Constants;
import com.shanghai.shanghaielectric.DB.LitePalHelper;

import org.apache.commons.lang3.ArrayUtils;
import org.greenrobot.eventbus.EventBus;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import shanghaielectric_lib.Communication.PortHelper.PortHelper;
import shanghaielectric_lib.DB.DBHelper_config;
import shanghaielectric_lib.DeviceConfigModel.Device.Device;
import shanghaielectric_lib.DeviceConfigModel.Device.DeviceModbusRTU;
import shanghaielectric_lib.DeviceConfigModel.Point.Point;
import shanghaielectric_lib.DeviceConfigModel.Point.ProtocolParam_Point_Modbus;
import shanghaielectric_lib.EnumDataHelper;
import shanghaielectric_lib.MyEventBus.EventBus_showCells;
import shanghaielectric_lib.MyEventBus.MyEventBusModel;
import shanghaielectric_lib.RDBHelper.Cell;
import shanghaielectric_lib.RDBHelper.Cell_show;
import shanghaielectric_lib.RDBHelper.RDBHelper;
import shanghaielectric_lib.Utils.ByteUtils;
import shanghaielectric_lib.Utils.CRC16;
import shanghaielectric_lib.Utils.LogUtil;
import shanghaielectric_lib.Utils.MyArrayUtils;
import shanghaielectric_lib.Utils.MySystemUtil;


/**
 * Created by 45000460 on 2017/11/8.
 */

public class Comm_Modbus {
    public static SerialPort serialPort;
    private InputStream mInputStream;
    private OutputStream mOutputStream;
    private Map<Integer, Integer> readDataByteLength = new LinkedHashMap<>();

    private List<Device> deviceList;

    public Comm_Modbus() {
        deviceList = DBHelper_config.getDBHelper_config().getDeviceList();
        initModbus(9600);
    }


    private void initModbus(int speed) {
        try {
            serialPort = PortHelper.getSerialPort(speed);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mInputStream = serialPort.getInputStream();
        mOutputStream = serialPort.getOutputStream();

        for (int i = 0; i < deviceList.size(); i++) {
            int length = 0;
            for (int j = 0; j < deviceList.get(i).pointList.size(); j++) {
                if (deviceList.get(i).pointList.get(j).isRead) {
                    if (EnumDataHelper.MODBUS_DATA_TYPE.READ_SHORT.equals(((ProtocolParam_Point_Modbus) deviceList.get(i).pointList.get(j).protocolParam_point).modbusDataType)) {
                        length = length + 2;
                    } else if (EnumDataHelper.MODBUS_DATA_TYPE.READ_INT.equals(((ProtocolParam_Point_Modbus) deviceList.get(i).pointList.get(j).protocolParam_point).modbusDataType)) {
                        length = length + 4;
                    }
                }
            }
            LogUtil.d("comm_modbus_初始化 ", "=" + length);
            readDataByteLength.put(deviceList.get(i).deviceID_d, length);
        }
    }

    /**
     * 请求数据
     */
    public void sendRequest(int deviceID) {
        Device device = DBHelper_config.getDBHelper_config().getDevices().get(deviceID);
        Map<Integer, Map<Integer, Cell>> cellsCollection = RDBHelper.getInstance().getCellsCollection();
        //cellMap
        Map<Integer, Cell> cellMap_read = new LinkedHashMap<>();
        List<Point> pointList_read = new ArrayList<>();
        for (int i = 0; i < device.pointList.size(); i++) {
            //说明该点，读数据点
            if (device.pointList.get(i).isRead) {
                cellMap_read.put(device.pointList.get(i).pointID, cellsCollection.get(deviceID).get(device.pointList.get(i).pointID));
                pointList_read.add(device.pointList.get(i));
            }
        }
        int startAddress_read = getStartAddress_read(pointList_read);//the min address of the device's points  is regard as startAddress
/**
 * get request byte[]
 */
//TODO  read start address
        byte[] requestBytes = getRequestBytes(((DeviceModbusRTU) device).modbusRTUParam.slaveID, Constants_modbus.READ_START_ADDRESS, pointList_read.size());
//        LogUtil.e("request",""+MyArrayUtils.byteArrayToHexStr(requestBytes));
        try {
            mOutputStream.write(requestBytes);
            LogUtil.e("comm_modbus", "请求数据");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     *  read modbus data according to deviceTypeID
     */
    /**
     * modbus 协议----获得通信数据
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    public void receiveCommuntcateData_Modbus(int deviceID, byte[] receiveDatas) {
        //TODO ---- 解析数据，建立在point点地址是连续不断地

        //考虑协议开始地址问题,pointID是连续的
        Device device = DBHelper_config.getDBHelper_config().getDevices().get(deviceID);
        //考虑modbus数据类型，决定取多少个byte[];

        int addressIndex = 0;

        for (int i = 0; i < device.pointList.size(); i++) {
            int count = 0;//check  points is valued  or not
            if (device.pointList.get(i).isRead) {
                if (EnumDataHelper.MODBUS_DATA_TYPE.READ_SHORT.equals(((ProtocolParam_Point_Modbus) device.pointList.get(i).protocolParam_point).modbusDataType)) {
                    byte[] tempBytes = new byte[]{receiveDatas[addressIndex], receiveDatas[addressIndex + 1]};
                    //向RDB插入数据
                    if (device.pointList.get(i).pointID == 62) {
                        LogUtil.d("logn", "=======" + device.pointList.get(i).pointID);
                    }
                    RDBHelper.getInstance().putPointValue_commValue(deviceID, device.pointList.get(i).pointID, ByteUtils.byte2ToShort(tempBytes));
                    addressIndex = addressIndex + 2;
                    count++;
                } else if (EnumDataHelper.MODBUS_DATA_TYPE.READ_INT.equals(((ProtocolParam_Point_Modbus) device.pointList.get(i).protocolParam_point).modbusDataType)) {
                    byte[] tempBytes = new byte[]{receiveDatas[addressIndex], receiveDatas[addressIndex + 1], receiveDatas[addressIndex + 2], receiveDatas[addressIndex + 3]};
                    RDBHelper.getInstance().putPointValue_commValue(deviceID, device.pointList.get(i).pointID, ByteUtils.byte4ToInt(tempBytes));
                    addressIndex = addressIndex + 4;
                    count++;
                } else {
//TODO 待补充--
                }

            }
        }
//        LogUtil.e("解析完成", "" + receiveDatas.length);
        Map<Integer, Map<Integer, Cell_show>> c = RDBHelper.getInstance().getCellsCollection_show();
        //发布数据：
        EventBus_showCells eventBus_showCells = new EventBus_showCells();
        eventBus_showCells.deviceID = deviceID;
        eventBus_showCells.cell_showMap = RDBHelper.getInstance().getCellsCollection_show(deviceID);
        EventBus.getDefault().post(eventBus_showCells);
        LitePalHelper.insertErrorInfo(eventBus_showCells);
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    synchronized public boolean readData(int deviceID) {
        if (deviceID == 7) {
            LogUtil.e("", "");
        }
        boolean isCommunicateSucceed = false;
        int length = readDataByteLength.get(deviceID) + 5;
        int size = 0;
        byte[] receiveData = new byte[length];
        boolean isConcat = false;
        boolean isHead = false;
        byte[] buf = new byte[181];
        if (mInputStream == null)
            return false;
        try {
            if (mInputStream.available() != 0) {
                size = mInputStream.read(buf);
            }
        } catch (
                IOException e) {
            e.printStackTrace();
        }
        LogUtil.e("comm_modbus", "读数据2");
//        LogUtil.e("thread", "执行了没" + size);
        if (size > 1) {
            byte[] dest = new byte[size];
            System.arraycopy(buf, 0, dest, 0, size);
            // TODO: 2017/8/16 说明tempByte是数据包第一帧部分数据
            if ((dest[0] >= 1 && dest[0] <= 8) && (dest[1] == 3)) {//    java.lang.ArrayIndexOutOfBoundsException: length=1; index=1
                receiveData = dest;
                isHead = true;
                isConcat = true;
            } else {
                isHead = false;
            }
            if (isConcat) {
                if (isHead) {
                    isHead = false;
                } else {
                    receiveData = ByteUtils.concat(receiveData, dest);
                }
            }
            LogUtil.e("收到数据--", "" + MyArrayUtils.byteArrayToHexStr(receiveData));
//            LogUtil.e("收到数据--", "" + receiveData.length+"---"+length);
            // TODO: 2017/8/16 0016 未校验数据是否正确
            if (receiveData.length == length) {
                if (0 < receiveData[0] && receiveData[0] < 9) {
                    if (CRC16.checkCRC16_bytes(receiveData)) {
                        LogUtil.e("crc", "收到数据crc正确");
                        //接收到的数据
                        LogUtil.d("收到数据0", "" + MyArrayUtils.byteArrayToHexStr(receiveData));
                        receiveCommuntcateData_Modbus(deviceID, ArrayUtils.subarray(receiveData, 3, receiveData.length - 2));
//                    byte[] bytes=ArrayUtils.subarray(receiveData, 3, receiveData.length - 2);
//                    LogUtil.d("收到数据1截取后：", "" + bytes.length);
//                    LogUtil.d("收到数据1", "" + MyArrayUtils.byteArrayToHexStr(bytes));
//                    LogUtil.e("","co");
                        isCommunicateSucceed = true;
                    } else {
                        LogUtil.e("crc", "收到数据crc错误"+MyArrayUtils.byteArrayToHexStr(receiveData));
                    }
                }
            }
        }
        return isCommunicateSucceed;
    }

    /**
     * 向串口写数据（特殊情况下制定，分包下发）
     */
    synchronized public void writeData(int deviceID, int packageID) {
        Device device = DBHelper_config.getDBHelper_config().getDevices().get(deviceID);
        Map<Integer, Cell> cellsCollection = RDBHelper.getInstance().getCellsCollection(deviceID);
        Map<Integer, Cell> cellMap = new LinkedHashMap<>();
//        List<Integer> listPointid_Write = new ArrayList<>();
        List<Point> pointList = new ArrayList<>();//save the point of write
        int startAddress = Constants_modbus.WRITE_START_ADDRESS;
        for (int i = 0; i < device.pointList.size(); i++) {
            if (!device.pointList.get(i).isRead) {
                int pointID_write = device.pointList.get(i).pointID;
                cellMap.put(pointID_write, cellsCollection.get(pointID_write));
//                listPointid_Write.add(pointID_write);
                pointList.add(device.pointList.get(i));
            }
        }
        int length = pointList.size();
        byte[] writeBytes_original = new byte[]{};
        byte[] bytes = new byte[]{};
        for (int i = (packageID - 1) * 10; i < packageID * 10; i++) {
            if (EnumDataHelper.MODBUS_DATA_TYPE.READ_WRITE_SHORT.equals(((ProtocolParam_Point_Modbus) pointList.get(i).protocolParam_point).modbusDataType)) {
                short pointValue = (short) Math.round((double) (cellMap.get(pointList.get(i).pointID).commValue));
                bytes = ByteUtils.shortToByte2(pointValue);
            } else if (EnumDataHelper.MODBUS_DATA_TYPE.READ_WRITE_INT.equals(((ProtocolParam_Point_Modbus) pointList.get(i).protocolParam_point).modbusDataType)) {
                int pointValue = (short) (int) cellMap.get(pointList.get(i).pointID).commValue;
                bytes = ByteUtils.intToByte4(pointValue);
            } else {
//TODO 可能需要补充
            }
            writeBytes_original = ArrayUtils.addAll(writeBytes_original, bytes);
        }
        try {
//            byte[] request = getWriteBytes(((DeviceModbusRTU) device).modbusRTUParam.slaveID, 4000+(packageID-1)*10, 10, writeBytes_original);
            byte[] request = getWriteBytes(Constants_modbus.WRITE_SLAVE_ID, startAddress + (packageID - 1) * 10, 10, writeBytes_original);

            mOutputStream.write(request);
            LogUtil.e("comm_modbus", "写数据");

            LogUtil.e("write request###---:"+packageID, "" + MyArrayUtils.byteArrayToHexStr(request));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 向串口写数据
     */
    synchronized public void writeData(int deviceID) {
        Device device = DBHelper_config.getDBHelper_config().getDevices().get(deviceID);
        Map<Integer, Cell> cellsCollection = RDBHelper.getInstance().getCellsCollection(deviceID);
        Map<Integer, Cell> cellMap = new LinkedHashMap<>();
//        List<Integer> listPointid_Write = new ArrayList<>();
        List<Point> pointList = new ArrayList<>();//save the point of write
        int startAddress = Constants_modbus.WRITE_START_ADDRESS;
        for (int i = 0; i < device.pointList.size(); i++) {
            if (!device.pointList.get(i).isRead) {
                int pointID_write = device.pointList.get(i).pointID;
                cellMap.put(pointID_write, cellsCollection.get(pointID_write));
//                listPointid_Write.add(pointID_write);
                pointList.add(device.pointList.get(i));
            }
        }
        int length = pointList.size();
        byte[] writeBytes_original = new byte[]{};
        byte[] bytes = new byte[]{};
        for (int i = 0; i < length; i++) {
            if (EnumDataHelper.MODBUS_DATA_TYPE.READ_WRITE_SHORT.equals(((ProtocolParam_Point_Modbus) pointList.get(i).protocolParam_point).modbusDataType)) {
                short pointValue = (short) Math.round((double) (cellMap.get(pointList.get(i).pointID).commValue));
                bytes = ByteUtils.shortToByte2(pointValue);
            } else if (EnumDataHelper.MODBUS_DATA_TYPE.READ_WRITE_INT.equals(((ProtocolParam_Point_Modbus) pointList.get(i).protocolParam_point).modbusDataType)) {
                int pointValue = (short) (int) cellMap.get(pointList.get(i).pointID).commValue;
                bytes = ByteUtils.intToByte4(pointValue);
            } else {
//TODO 可能需要补充
            }
            writeBytes_original = ArrayUtils.addAll(writeBytes_original, bytes);
        }
        try {
            byte[] request = getWriteBytes(((DeviceModbusRTU) device).modbusRTUParam.slaveID, startAddress, cellMap.size(), writeBytes_original);
            mOutputStream.write(request);
//            LogUtil.e("write request", "" + MyArrayUtils.byteArrayToHexStr(request));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * get request data    : byte[]
     * 请求数据格式： deviceTypeID - slaveID - startAddress - byteCount - CRC [ low ,high ]
     */
    private static byte[] getRequestBytes(int slaveID, int startAddress, int byteCount) {
// CRC16
        byte[] crcBytes = ArrayUtils.addAll(ArrayUtils.addAll(ArrayUtils.addAll(new byte[]{(byte) slaveID, (byte) 3}, ByteUtils.shortToByte2((short) startAddress)), ByteUtils.shortToByte2((short) byteCount)));
        return ArrayUtils.addAll(crcBytes, ByteUtils.shortToByte2((short) CRC16.calcCrc16(crcBytes, 0, crcBytes.length)));
    }

    /**
     * 将原始数据，转化为modbus格式数据----originalData为已经转化为byte[]的数据（）
     */
    private static byte[] getWriteBytes(int slaveID, int startAddress, int registerCount, byte[] originalData) {
// CRC16
        byte[] crcBytes =
                ArrayUtils.addAll(
                        ArrayUtils.addAll(
                                ArrayUtils.addAll(
                                        ArrayUtils.addAll(
                                                new byte[]{(byte) slaveID, (byte) 16},
                                                ByteUtils.shortToByte2((short) startAddress)),
                                        ByteUtils.shortToByte2((short) registerCount)),
                                (byte) originalData.length),
                        originalData);
        return ArrayUtils.addAll(crcBytes, ByteUtils.shortToByte2((short) CRC16.calcCrc16(crcBytes, 0, crcBytes.length)));
    }

    public List<Integer> getMapKeys(Map<Integer, Cell> map) {
        List list = new ArrayList();
        Set<Map.Entry<Integer, Cell>> entries = map.entrySet();
        for (Map.Entry<Integer, Cell> entry : entries) {
            list.add(entry.getKey());
//            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        return list;
    }

    public int getStartAddress_read(List<Point> pointList_read) {
        int startAddress = (pointList_read.get(0).protocolParam_point).address;
        for (Point point : pointList_read) {
            startAddress = startAddress > point.protocolParam_point.address ? point.protocolParam_point.address : startAddress;
        }
        return startAddress;
    }

    public void closePort() {
        PortHelper.closeSerialPort();
    }
}
